package com.gitee.wsl.io.buffer.ext

import com.gitee.wsl.io.buffer.ByteBuffer
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract


@OptIn(ExperimentalContracts::class)
inline fun ByteBuffer.commonUpdate(
    input: Byte,
    bufPosPlusPlus: Int,
    bufPosSet: (zero: Int) -> Unit,
    compressProtected: (ByteArray, Int) -> Unit,
) {
    contract {
        callsInPlace(bufPosSet, InvocationKind.AT_MOST_ONCE)
        callsInPlace(compressProtected, InvocationKind.AT_MOST_ONCE)
    }

    buf[bufPosPlusPlus] = input

    // buf.size == blockSize
    if ((bufPosPlusPlus + 1) != buf.size) return
    compressProtected(buf, 0)
    bufPosSet(0)
}

@OptIn(ExperimentalContracts::class)
inline fun ByteBuffer.commonUpdate(
    input: ByteArray,
    offset: Int,
    len: Int,
    bufPos: Int,
    bufPosSet: (value: Int) -> Unit,
    compressProtected: (ByteArray, Int) -> Unit,
) {
    contract {
        callsInPlace(bufPosSet, InvocationKind.EXACTLY_ONCE)
        callsInPlace(compressProtected, InvocationKind.UNKNOWN)
    }

    val blockSize = buf.size
    val limitInput = offset + len
    var posInput = offset
    var posBuf = bufPos

    if (posBuf > 0) {
        // Need to use buffered data (if possible)

        if (posBuf + len < blockSize) {
            // Not enough for a compression. Add it to the buffer.
            input.copyInto(buf, posBuf, posInput, limitInput)
            bufPosSet(posBuf + len)
            return
        }

        // Add enough input to do a compression
        val needed = blockSize - posBuf
        input.copyInto(buf, posBuf, posInput, posInput + needed)
        compressProtected(buf, 0)
        posBuf = 0
        posInput += needed
    }

    // Chunk blocks (if possible)
    while (posInput < limitInput) {
        val posNext = posInput + blockSize

        if (posNext > limitInput) {
            // Not enough for a compression. Add it to the buffer.
            input.copyInto(buf, 0, posInput, limitInput)
            posBuf = limitInput - posInput
            break
        }

        compressProtected(input, posInput)
        posInput = posNext
    }

    // Update globals
    bufPosSet(posBuf)
}